#include <iostream>
#include <fstream>
#include <cmath>
#include <cstdio>
#include <string>
#include <iomanip>

#include "shekel_function.h"
#include "griewank_function.h"
#include "rosenbrock_function.h"
#include "zakharov_function.h"
#include "B2_function.h"

#include "continuous_tabusearch.h"
#include "Time.h"
#include "wrapperFunctions.h"

#define PI   3.1415926535897932385

using namespace std;

void run_CTS(int argc, char *argv[ ], Function *func, string& directory)
{
  int seed;                 // seed for random number
  int numNeighPoint;        // number of neighbouring solutions
  double radiusTabuSphere;  // radius of the tabu hypersphere
  double radiusPromSphere;  // radius of the promising hypersphere
  double biggestRadius;     // radius of the biggest hypersphere
  int maxTabuListSize;      // size of the tabu list
  int maxPromlistSize;      // size of the promising list
  int numRun;               // number of runs
  int numDim;	              // the number of dimensions
  int maxIteration;         // max number of iterations



  /////////////////////////////////////////////////////
  // Get input parameters
  /////////////////////////////////////////////////////

  if (argc == 11)
  {
    // required content of input arguments
    seed = atoi(argv[1]);
    numNeighPoint = atoi(argv[2]);
    radiusTabuSphere = atof(argv[3]);
    radiusPromSphere = atof(argv[4]);
    biggestRadius = atof(argv[5]);    
    maxTabuListSize = atoi(argv[6]);
    maxPromlistSize = atoi(argv[7]);
    numRun = atoi(argv[8]);
    numDim = atoi(argv[9]);
    maxIteration = atoi(argv[10]);
  }
  else if (argc == 1)  // if no arguments given to executable do this
  {
    ifstream file("input/params_cts.txt");

    if (! file)
    {
      cout << "Cannot open params.in\n";
      exit(1);
    }

    file >> seed >> numNeighPoint >> radiusTabuSphere >> radiusPromSphere
      >> biggestRadius >> maxTabuListSize >> maxPromlistSize >> numRun >> numDim >> maxIteration;

    file.close();
  }
  else
  {
    cout << "Incorrect input.\n";
    exit(1);
  }



  ///////////////////////////////////////
  // write out input parameters to file
  ///////////////////////////////////////

  ofstream fpParams("./output/CTS_params.used");
  fpParams << "Seed              = " << seed << endl;
  fpParams << "numNeighPoint     = " << numNeighPoint << endl;
  fpParams << "radiusTabuSphere  = " << radiusTabuSphere << endl;
  fpParams << "radiusPromSphere  = " << radiusPromSphere << endl;
  fpParams << "biggestRadius     = " << biggestRadius << endl;
  fpParams << "maxTabuListSize   = " << maxTabuListSize << endl;
  fpParams << "maxPromlistSize   = " << maxPromlistSize << endl;
  fpParams << "numRun            = " << numRun << endl;
  fpParams << "numDim            = " << numDim << endl;
  fpParams << "maxIteration      = " << maxIteration << endl;

  fpParams.close();

  initiate_ran1(seed);      // initiates the random number

  Time timer;               // to measure calculation times
  cout << "prepare timer " << timer;

  double *sol;              // the solutions we will follow
  double *bestSol;          // the progress of during CTS
  double gloBest = 1e16;
  //gloBest = 1e16;



  ////////////////////////////////////////////////////////////////
  // Open files CTS_Solution.out and CTS_initial_values.out,
  // and prepare them for output. CTS_Solution.out holds the
  // best function value after each run both without local
  // minimisation and with local minimisation. CTS_initial_values
  // holds the initial coordinates (debugging).
  ////////////////////////////////////////////////////////////////

  ofstream outEndCTS_Solution( string(directory + "CTS_Solution.out").c_str());

  outEndCTS_Solution << "Dimension: " << numDim << endl
    << "Smallest function value after each run\n"
	  << "(LM = Local Minimization)\n\n"
	  << "     Func Val Bef           Func Val Aft           Function Evaluations          Distance"
    << "          Run Number\n\n";

  ofstream outIniVal("output/CTS_initial_values.out");

  Function *function;       // Creates an instance of the function
  function = func;          // Sets the function = to the function passed as an argument

  newA(sol, numDim);
  newA(bestSol, numDim);



  ////////////////////////////////////////////
  // creates a CTS object with the specified
  // arguments; get the time; and begin a run
  ////////////////////////////////////////////

  ContinuousTabuSearch cts(numDim, numNeighPoint, biggestRadius, radiusTabuSphere, radiusPromSphere, 
                        maxTabuListSize, maxPromlistSize, function);

  double bestFuncValue;

  cout << "Time: " << timer << endl;

	for(int i=1; i<=numRun; i++)  
	{
    cout << "Run No: " << i << endl;

    // Populate the Input array which a random start position/configuration
    cts.Initialise(biggestRadius, radiusTabuSphere, radiusPromSphere);
    function->nCall = 0;
		function->createRandomSol(sol);

    // Print out the run number and initial
    // starting configuration (coordinates)
		outIniVal << "\nRun No. " << i << endl << endl;
    outIniVal << "Dimension | Starting values" << endl;
		for (int k=1; k<=numDim; k++)
    {
      outIniVal << setw(5) << k << setw(18) << sol[k] << endl;
    }

    // reset tracker before starting a new CTS run
		cts.ResetTracker(sol);            // calculates the function value of sol

    outEndCTS_Solution << setw(14) << function->Value(sol);

    function->nCall--;                // decreases nCall because funcval of sol is called in ResetTracker

		// used to diagnose the program (only used in doRunDiagnosis)
		char CTSfuncSample[80]; 
		sprintf_s(CTSfuncSample, 80, string(directory + "CTS_diagnosis%d.out").c_str(), i);
    char CTScoordSample[80];
    sprintf_s(CTScoordSample, 80, string(directory + "CTS_coord%d.coord").c_str(), i);

    //cts.doRun(sol, maxIteration, directory);           // do a run
    cts.doRunDiagnosis(sol, maxIteration, CTSfuncSample, CTScoordSample, directory);

    // print out best solution (before local minimisation)
		//char resultCTS[80];
		/*sprintf_s(resultCTS, 80, string(directory + "result_CTS%d.txt").c_str(), i);
    function->print_sol();*/

    // print out best solution so far (without local minimisation)
    double lBestFuncValue = function->BestSoFar(bestSol); 
    //outEndCTS_Solution << setw(20) << lBestFuncValue;

		// print out best solution (after local minimisation)
    //wrapperCGM(bestSol,numDim,1e-3,&lBestFuncValue,function);  // calls local minimisation
		/*sprintf_s(resultCTS, 80, string(directory + "CTS_result%d.txt").c_str(), i);
		function->print_sol(bestSol,lBestFuncValue, resultCTS, 0, 0);*/

    // after local minimisation lBestFuncValue has been changed
    // this sets lBestFuncValue equal to bestFuncValue which is
    // then outputted
    bestFuncValue = lBestFuncValue;
	  outEndCTS_Solution << setw(20) << bestFuncValue << setw(21) << function->nCall << setw(26) 
        << function->distanceFromGlobalMinimum(bestSol) << setw(16) << i << endl;

    if(gloBest > bestFuncValue)
    {
      gloBest = bestFuncValue;
    }
  }

  outEndCTS_Solution << "\nGlobal best value over all runs:\t" << setw(9) << gloBest << endl;

  outEndCTS_Solution.close();
  outIniVal.close();

  // print final output to screen
  cout << endl << endl << "Output files have been written to " << directory << endl;
  cout << "The total time for the simulation was " << timer << endl << endl;
  cout << "---------------------------" << endl << endl;
}